import { Injectable } from "@angular/core";
import { Cipher as CryptoCipher, Encoder as CipherEncoder, AES, MD5, SHA256, SHA512, LibWordArray, enc, lib } from "crypto-js";

/**
 * 数据加密服务
 * 
 * @export
 * @class CryptoService
 */
@Injectable()
export class CryptoService {
    constructor() {
    }

    /**
     * 获取AES加密工具实例
     * 
     * @readonly
     * @type {Cipher}
     * @memberof CryptoService
     */
    public get aes(): Cipher {
        return new AESProxyCipher();
    }

    /**
     * 获取MD5哈希工具实例
     * 
     * @readonly
     * @type {Hash}
     * @memberof CryptoService
     */
    public get md5(): Hash {
        return new MD5ProxyHash();
    }

    /**
     * 获取SHA256哈希工具实例
     * 
     * @readonly
     * @type {Hash}
     * @memberof CryptoService
     */
    public get sha256(): Hash {
        return new SHA256ProxyHash();
    }

    /**
     * 获取SHA512哈希工具实例
     * 
     * @readonly
     * @type {Hash}
     * @memberof CryptoService
     */
    public get sha512(): Hash {
        return new SHA512ProxyHash();
    }

    /**
     * 获取Base64转换工具实例
     * 
     * @readonly
     * @type {Encoder}
     * @memberof CryptoService
     */
    public get base64(): Encoder {
        return new Base64ProxyEncode();
    }

    /**
     * 获取Hex转换工具实例
     * 
     * @readonly
     * @type {Encoder}
     * @memberof CryptoService
     */
    public get hex(): Encoder {
        return new HexProxyEncode();
    }
}

export interface Cipher {

    /**
     * 执行加密
     * 
     * @param {string} data 待加密数据
     * @param {string} password 密码
     * @returns {string} 
     * @memberof Cipher
     */
    encrypt(data: string, password: string): string;

    /**
     * 执行解密
     * 
     * @param {string} data 待解密数据
     * @param {string} password 密码
     * @returns {string} 
     * @memberof Cipher
     */
    decrypt(data: string, password: string): string;
}

export interface Hash {
    /**
     * 执行哈希摘要
     * 
     * @param {string} message 待计算数据
     * @returns {string} 
     * @memberof Hash
     */
    digest(message: string): string;
}

export interface Encoder {

    /**
     * 将编码后字符串转为字节数组
     * 
     * @param {string} encoded 
     * @returns {Uint8Array} 
     * @memberof Encoder
     */
    parse(encoded: string): Uint8Array;

    /**
     * 将字节数组转为特定字符串表示
     * 
     * @param {Uint8Array} words 
     * @returns {string} 
     * @memberof Encoder
     */
    stringify(words: Uint8Array): string;
}

function isStringEmpty(value: string) {
    return (value === null || typeof value === "undefined" || value === "" || value.trim() === "");
}

class ProxyCipher implements Cipher {

    constructor(private proxy: CryptoCipher) {
    }

    encrypt(data: string, password: string): string {
        if (isStringEmpty(data) || isStringEmpty(password)) {
            return "";
        }
        return this.proxy.encrypt(data, password).toString();
    }

    decrypt(data: string, password: string): string {
        if (isStringEmpty(data) || isStringEmpty(password)) {
            return "";
        }
        return this.proxy.decrypt(data, password).toString(enc.Utf8);
    }
}

class AESProxyCipher extends ProxyCipher {
    constructor() {
        super(AES);
    }
}

class MD5ProxyHash implements Hash {
    digest(message: string): string {
        if (isStringEmpty(message)) {
            return "";
        }
        return MD5(message).toString(enc.Hex);
    }
}

class SHA256ProxyHash implements Hash {
    digest(message: string): string {
        if (isStringEmpty(message)) {
            return "";
        }
        return SHA256(message).toString(enc.Hex);
    }
}

class SHA512ProxyHash implements Hash {
    digest(message: string): string {
        if (isStringEmpty(message)) {
            return "";
        }
        return SHA512(message).toString(enc.Hex);
    }
}

class ProxyEncode implements Encoder {

    constructor(private proxy: CipherEncoder) {
    }

    parse(encoded: string): Uint8Array {
        if (isStringEmpty(encoded)) {
            return new Uint8Array(0);
        }

        const result: LibWordArray = this.proxy.parse(encoded);
        const array: number[] = [];
        for (let i = 0; i < result.sigBytes; i++) {
            const bite: number = (result.words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; // tslint:disable-line
            array.push(bite);
        }

        return Uint8Array.from(array);
    }

    stringify(words: Uint8Array): string {
        if (words === null || typeof words === "undefined" || words.byteLength === 0) {
            return "";
        }

        const array: LibWordArray = lib.WordArray.create(words);
        return this.proxy.stringify(array);
    }

}

class Base64ProxyEncode extends ProxyEncode {
    constructor() {
        super(enc.Base64);
    }
}

class HexProxyEncode extends ProxyEncode {
    constructor() {
        super(enc.Hex);
    }
}
